/*
Hermes Core: General Purpose Mobile Agent Platform
Copyright (C) 2006 UNICAM - Università degli Studi di Camerino

Group leaders: Flavio Corradini and Emanuela Merelli

Developers: Ezio Bartocci, Diego Bonura, Rosario Culmone,
Davide Lorenzoli, Leonardo Mariani, Francesca Piersigilli, 
Lorenzo Scortichini, Leonardo Vito.

GNU Lesser Public License

This library is free software; you can redistribute it 
and/or modify it under the terms of the GNU Lesser General 
Public License as published by the Free Software Foundation, 
version 2.1 of the License.

This library is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public 
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

Emanuela Merelli
Dipartimento di Matematica ed Informatica
Università di Camerino
via Madonna delle Carceri, 62032 Camerino, Italy
E.Mail: emanuela.merelli@unicam.it
*/
package hermesV2.agent;

import java.io.Serializable;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;

/**
 * @author diegobonura
 * @author leonardovito
 * 
 * This class could be used for the agent to agent interactions. Could be
 * supports a protocol.
 *  
 */

public class InteractionContainer implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -4820437517967020296L;

	/** <CODE>Static</CODE> is a static counter. */
	private static int serial;

	/**
	 * Contains all information about the specific interaction.
	 */
	private Hashtable<String, Object> parameters;

	/**
	 * This is the action of the interaction.
	 */
	private String action;

	/**
	 * This is the type of the interaction (usually Response, Request or Error)
	 */
	private String type;

	/**
	 * This is the identificator of the conversation, it could be usefull when
	 * there are long transactions
	 */
	private String conversationId;

	/**
	 * The name of the interaction set. Could be null.
	 */
	private String interactionName;

	/**
	 * The protocol of the interaction.
	 */
	private AbstractProtocol protocol;

	/**
	 * This is the constructor of the class. It initializes the parameter
	 * hashtable.
	 */
	public InteractionContainer() {
		parameters = new Hashtable<String, Object> ();
		//Sets the conversetionID by time
		conversationId = Long.toString(new Date((new Date()).getTime())
				.getTime())
				+ serial++;
	}

	/**
	 * This is the constructor of the class that takes the protocol of the
	 * InteractionContainer
	 * 
	 * @param protocol
	 *            The protocol of the InteractionContainer.
	 */
	public InteractionContainer(AbstractProtocol protocol) {
		parameters = new Hashtable<String, Object> ();
		this.protocol = protocol;
		//Sets the conversetionID by time
		conversationId = Long.toString(new Date((new Date()).getTime())
				.getTime())
				+ serial++;
		;
	}

	/**
	 * Sets the protocol to the InteractionContainer
	 * 
	 * @param protocol
	 *            The protocol of the InteractionContainer.
	 */
	public void setProtocol(AbstractProtocol protocol) {
		this.protocol = protocol;
	}

	/**
	 * Makes the interaction giving the name. This method sets all the
	 * parameters.
	 * 
	 * @param interactionName
	 *            The name of interaction to set into the InteractionContainer
	 * @param parameterObjects
	 *            The array of parameters respecting the protocol.
	 */
	public void makeInteraction(String interactionName,
			Object[] parameterObjects) {
		this.eraseAll();
		if (protocol != null) {
			Interaction i = protocol.getInteraction(interactionName);
			if (i != null) {
				String[] parameterNames = i.getParameterNames();
				Class<?>[] parameterTypes = i.getParameterTypes();
				if (parameterObjects.length == parameterTypes.length) {
					this.interactionName = interactionName;
					this.setAction(i.getAction());
					this.setType(i.getType());
					for (int j = 0; j < parameterNames.length; j++) {
					    if (parameterTypes[j].equals(Object.class)) {
							this.setParameter(parameterNames[j],
									parameterObjects[j]);
						} else if (parameterTypes[j].equals(Object[].class)) {
							if (parameterObjects[j].getClass().isArray()){
								this.setParameter(parameterNames[j],
										parameterObjects[j]);
							}
						} else if (parameterObjects[j].getClass().equals(
								parameterTypes[j])) {
							this.setParameter(parameterNames[j],
									parameterObjects[j]);

						}
					}
				} else {
				    System.out.println("[InteractionContainer error set parameters]");
					this.eraseAll();
				}
			}
		}
	}

	/**
	 * This method changes the current InteractionContainer attributes to make
	 * the right response of the current interaction.
	 * 
	 * @param parameterObjects
	 *            The array of parameters respecting the protocol.
	 */
	public void makeResponseInteraction(Object[] parameterObjects) {
		if (interactionName != null
				&& this.protocol
						.getResponseInteractionLink(this.interactionName) != null)
			makeInteraction(this.protocol.getResponseInteractionLink(
					this.interactionName).getName(), parameterObjects);
	}

	/**
	 * This method changes the current InteractionContainer attributes to make
	 * the right response of the current interaction.
	 */
	public void makeResponseInteraction() {
		if (interactionName != null
				&& this.protocol
						.getResponseInteractionLink(this.interactionName) != null)
			makeInteraction(this.protocol.getResponseInteractionLink(
					this.interactionName).getName(), new Object[0]);
	}

	/**
	 * This method changes the current InteractionContainer attributes to make
	 * an error response of the current interaction.
	 * 
	 * @param parameterObjects
	 *            The array of parameters respecting the protocol.
	 */
	public void makeErrorInteraction(Object[] parameterObjects) {
		if (interactionName != null
				&& this.protocol.getErrorInteractionLink(this.interactionName) != null)
			makeInteraction(this.protocol.getErrorInteractionLink(
					this.interactionName).getName(), parameterObjects);
	}

	/**
	 * This method changes the current InteractionContainer attributes to make
	 * an error response of the current interaction.
	 */
	public void makeErrorInteraction() {
		if (interactionName != null
				&& this.protocol.getErrorInteractionLink(this.interactionName) != null)
			makeInteraction(this.protocol.getErrorInteractionLink(
					this.interactionName).getName(), new Object[0]);
	}

	/**
	 * Returns the name of the current interaction sets into the
	 * InteractionContainer.
	 * 
	 * @return The name of the current interaction.
	 */
	public String getInteractionName() {
		if (this.interactionName == null && this.protocol != null) {
			Interaction[] interactions = this.protocol.getAllInteractions();
			int i = 0;
			boolean found = false;
			while (i < interactions.length && !found) {
				if (validateInteraction(interactions[i]))
					found = true;
				else
					i++;
			}
			if (found)
				this.interactionName = interactions[i].getName();
		} else {
		}
		return interactionName;
	}

	/**
	 * Returns the name of the current interaction sets into the
	 * InteractionContainer.
	 * 
	 * @param protocol
	 *            The protocol to respect.
	 * @return The name of the current interaction.
	 */
	public String getInteractionName(AbstractProtocol protocol) {
		if (this.interactionName == null && protocol != null) {
			Interaction[] interactions = protocol.getAllInteractions();
			int i = 0;
			boolean found = false;
			while (i < interactions.length && !found) {
				if (validateInteraction(interactions[i])) {
					found = true;
				} else
					i++;
			}
			if (found)
				this.interactionName = interactions[i].getName();
		} else {
		}
		return interactionName;
	}

	/**
	 * Validates a single interaction.
	 * 
	 * @param interaction
	 *            The interaction to validate.
	 * @return True if the interaction is well-formed false otherwise.
	 */
	private boolean validateInteraction(Interaction interaction) {
		boolean different = false;
		if (interaction.getType().equalsIgnoreCase(this.getType())
				&& interaction.getAction().equalsIgnoreCase(this.getAction())
				&& interaction.getParameterNames().length == this.parameters
						.size()) {
			int i = 0;
			while (i < parameters.size() && !different) {
				String tempString = interaction.getParameterNames()[i];
				if (parameters.containsKey(tempString)) {
					Object parameterValue = parameters.get(tempString);
					if (parameterValue != null) {
						if (!interaction.getParameterTypes()[i]
								.equals(Object.class) && !interaction.getParameterTypes()[i].equals(Object[].class)) {
							if (!parameterValue.getClass().equals(
									interaction.getParameterTypes()[i])) {
								different = true;
							}
						}
					}
				} else {
					different = true;
				}
				i++;
			}

		} else {
			different = true;
		}

		if (different) {
			this.interactionName = null;
		}
		return !different;
	}

	/**
	 * Changes the current InteractionContainer respecting the protocol.
	 * 
	 * @param interactionName
	 *            The name of the interaction.
	 * @param parameterObject
	 *            The array of parameters respecting the protocol.
	 */
	public void makeInteraction(String interactionName, Object parameterObject) {
		Object[] parameterObjects = new Object[1];
		parameterObjects[0] = parameterObject;
		makeInteraction(interactionName, parameterObjects);
	}

	/**
	 * Changes the current InteractionContainer respecting the protocol without
	 * parameters.
	 * 
	 * @param interactionName
	 *            The name of the interaction.
	 */
	public void makeInteraction(String interactionName) {
		Object[] parameterObjects = new Object[0];
		makeInteraction(interactionName, parameterObjects);
	}

	/**
	 * Checks the current InteractionProtocol and validate it respecting the
	 * InteractionName.
	 * 
	 * @return True if the InteractionControll is well-formed false otherwise.
	 */
	public boolean validate() {
		if (this.getInteractionName() == null)
			return false;
		return validateInteraction(this.protocol.getInteraction(this
				.getInteractionName()));
	}

	/**
	 * Checks the current InteractionProtocol and validate it respecting the
	 * InteractionName.
	 * 
	 * @param protocol
	 *            The protocol to respect.
	 * @return True if the InteractionControll is well-formed false otherwise.
	 */
	public boolean validate(AbstractProtocol protocol) {
		String interactionName = this.getInteractionName(protocol);
		if (interactionName == null)
			return false;
		else
			return validateInteraction(protocol.getInteraction(interactionName));
	}

	/**
	 * Returns the parameter linked by the <code>key</code>.
	 * 
	 * @param key
	 *            The key associated with the parameter.
	 * @return The value of the key.
	 */
	public Object getParameter(String key) {
		Enumeration<String> keys = parameters.keys();
		boolean found = false;
		String localKey;
		Object result = null;
		while (keys.hasMoreElements() && !found) {
			localKey = keys.nextElement();
			if (localKey.equalsIgnoreCase(key.toString())) {
				found = true;
				result = parameters.get(localKey);
			}

		}

		return result;

	}

	/**
	 * Return the identificator of the conversation.
	 * 
	 * @return The identificator of the conversation.
	 */
	public String getConversationId() {
		return new String(conversationId);
	}

	/**
	 * Sets the key associated with the parameter. If the key is already into
	 * the hashtable it will be replaced.
	 * 
	 * @param key
	 *            The key.
	 * @param value
	 *            The value.
	 */
	public void setParameter(String key, Object value) {
		parameters.put(key, value);

	}

	/**
	 * Override the hashtable parameters with the new one.
	 * 
	 * @param ps
	 *            The new parameters Hashtable.
	 *  
	 */
	public void setParameters(Hashtable<String, Object>  ps) {
		parameters = ps;
	}

	/**
	 * Appends the hashtable's parameters in input.
	 * 
	 * @param ps
	 *            The new parameters Hashtable to append.
	 *  
	 */
	public void appendParameters(Hashtable<String, Object>  ps) {
		parameters.putAll(ps);
	}

	/**
	 * Erases the entry (key,value) stored into the hashtable. If the key is not
	 * contained into the hashtable nothing changes.
	 * 
	 * @param key
	 *            The key to erase.
	 */
	public void eraseParameter(String key) {

		Enumeration<String> keys = parameters.keys();
		boolean found = false;
		String localKey;

		while (keys.hasMoreElements() && !found) {
			localKey = (String) keys.nextElement();
			if (localKey.equalsIgnoreCase(key.toString())) {
				found = true;
				parameters.remove(localKey);
			}

		}
	}

	/**
	 * Erases all the key into the hashtable.
	 */
	public void eraseAll() {
		parameters = new Hashtable<String, Object> ();
		action = null;
		type = null;
		interactionName = null;

	}

	/**
	 * Gets the current action
	 * 
	 * @return Returns the action.
	 */
	public String getAction() {
		return action;
	}

	/**
	 * Sets the current action.
	 * 
	 * @param action
	 *            The action to set.
	 */
	public void setAction(String action) {
		this.action = action;
	}

	/**
	 * Returns the type of the interaction.
	 * 
	 * @return Returns the type.
	 */
	public String getType() {
		return type;
	}

	/**
	 * Sets the type of interaction.
	 * 
	 * @param type
	 *            The type to set.
	 */
	public void setType(String type) {
		this.type = type;
	}

	/**
	 * Returns the protocol of the interaction container.
	 * 
	 * @return The protocol.
	 */
	public AbstractProtocol getProtocol() {
		return protocol;
	}

	/**
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return "IteractionName: " + this.getInteractionName() + " Action: "
				+ this.getAction() + " Type: " + this.getType();
	}

}